import { BaseMode } from "./interface";
import { Utils } from "./util";

let util = new Utils();
export class GenericTest implements BaseMode{
    run(){

        //generic function defined
        // using `any`, it can lose type information
        function identity<T>(arg: T): T {
            // util.log(arg.length);
            return arg;
        }

        //usage of generic function
        // set type
        let output = identity<string>("my name is tanson");
        // type argument inference 
        let output2 = identity("my name is tanson");

        function loggingIdentity<T>(arg: T[]): T[] {
            util.log(arg.length,"generic array type");  // Array has a .length, so no more error
            return arg;
        }

        // Generic Types
        // first generic type interface
        interface GenericIdentityFn<T>{
            (arg :T):T;
        }
        let myIdentity: GenericIdentityFn<string> = identity;

        // Generic Constraints
        // constraint the generic type, eg:
        interface iLength {
            length:number;
        }
        function constraintIdentity<T extends iLength>(arg: T): T {
            util.log(arg.length,"constraint generic type by extends inference");  // Array has a .length, so no more error
            return arg;
        }
        // let fun1 = constraintIdentity(3);//error
        let fun2 = constraintIdentity({length:10,value:"zx"});

        //Using Type Parameters in Generic Constraints
        // ver 2.1 code error,using `as`
        function copyFields<T extends U, U>(target: T, source: U): T {
            util.log(source,"src orign data");
            util.log(target,"dst orign data");
            for (let id in source) {
                target[id] = source[id] as T[keyof U];
            }   
            util.log(target,"dst new data");
            return target;
        }
        // ver 2.2 
        function getProperty<T, K extends keyof T>(obj: T, key: K) {
            return obj[key];
        }

        let x = { a: 1, b: 2, c: 3, d: 4 };
        copyFields(x, { b: 10, d: 20 }); // okay
        // copyFields(x, { Q: 90 });  // error: property 'Q' isn't declared in 'x'.
        getProperty(x, "a"); // okay
        // getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

        //Using Class Types in Generics
        // generic ctor functon
        function create<T>(c: {new(): T; }): T {
            return new c();
        }

        // another example
        class BeeKeeper {
            hasMask: boolean;
        }
        class ZooKeeper {
            nametag: string;
            constructor(){               
                this.nametag = "my nametag";
            }
        }
        class Animal {
            numLegs: number;
        }
        class Bee extends Animal {
            keeper: BeeKeeper;
        }
        class Lion extends Animal {
            keeper: ZooKeeper;
            constructor(){
                super();
                this.keeper = new ZooKeeper();
                this.keeper.nametag = "ZooKeeper nametag";
            }
        }

        function findKeeper<A extends Animal, K> (a: { //interface
            new(): A; 
            prototype: {keeper: K}
        }): K {
            let instance:any = new a();
            util.log(a,"complex interface using generic check type");
            util.log(instance,"complex interface using generic check type new ");
            util.log(a.prototype.keeper,"complex interface using generic check type property ");

            // return instance.keeper;
            return a.prototype.keeper;
        }
        // let keeper = findKeeper(Lion).nametag;  //just typechecks!
        // let instance = new keeper();
        // util.log(keeper.nametag,"just test");
       
    }
}